// Main entry point for the Rust blockchain project.

use std::io::Write;

use clap::Parser;
use tokio::{
    io::AsyncBufReadExt,
    sync::{mpsc, watch},
    time::Duration,
};
use tracing::{debug, info};

use bc_base::transaction::Transaction;
use bc_lib::mining::MiningCommand;
use bc_manager::{BcManager, BcManagerHandler};
use mini_block_chain::{Args, blockchain_logic, debug_cmd};

#[tokio::main]
async fn main() {
    let args = Args::parse();
    mini_block_chain::init_logger(&args);
    mini_block_chain::dump_system_info();

    // if (mapArgs.count("-loadblockindextest")) {
    //     CTxDB txdb("r");
    //     txdb.LoadBlockIndex();
    //     PrintBlockTree();
    //     return false;
    // }

    let db_root_dir = mini_block_chain::get_database_root_dir(&args);
    println!("Database root dir: {}", db_root_dir.to_str().unwrap());
    info!("Database root dir: {}", db_root_dir.to_str().unwrap());

    let (mining_manager, mining_cmd_receiver, transaction_receiver) =
        bc_lib::mining::MiningManager::new();

    // Create a watch channel for shutdown signal.
    // Initially, false means "keep running". When set to true, tasks should shut down.
    let (shutdown_tx, shutdown_rx) = watch::channel(false);

    let bc_manager = BcManager::new(
        db_root_dir,
        mining_manager,
        args.get_secret_key_seed(),
        args.get_p2p_listen_address(),
        shutdown_rx.clone(),
    )
    .await;
    blockchain_logic::check_genesis_info(bc_manager.clone()).await;

    let p2p_user_event_handler = bc_manager::start_process_p2p_user_event_task(
        bc_manager.clone(),
        shutdown_rx.clone(),
        shutdown_tx.clone(),
    )
    .await;

    bc_manager.lock().await.register_topic_of_block().await;
    bc_manager.lock().await.register_topic_of_tx().await;

    // start web server
    let task_web_server =
        mini_block_chain::start_web_server(&args, bc_manager.clone(), shutdown_rx.clone()).await;

    // start mining task
    let task_mine = start_mining_task(
        &args,
        bc_manager.clone(),
        mining_cmd_receiver,
        transaction_receiver,
        shutdown_rx.clone(),
    )
    .await;

    // listen termial input command
    let terminal_input_listener = start_input_listener_task(bc_manager.clone()).await;

    tokio::time::sleep(Duration::from_millis(100)).await; // wait above tasks to start

    // run blockchain core logic
    blockchain_logic::run_blockchain_logic(bc_manager.clone()).await;

    bc_util::print_task_and_thread_id("main thread");

    tokio::select! {
        // _ = frontend_server => {
        //     println!("Frontend server stopped");
        // },

        _ = terminal_input_listener => {
            info!("User entered 'exit'. Shutting down...");
            let _ = shutdown_tx.send(true);
        },

        _ = tokio::signal::ctrl_c() => {
            println!("Received Ctrl+C signal, stopping blockchain core logic...");
            let _ = shutdown_tx.send(true);
        },
    }

    task_web_server.await.unwrap();
    task_mine.join().unwrap();

    mini_block_chain::cleanup_before_exit(bc_manager.clone()).await;
    bc_manager.lock().await.shutdown().await;
    p2p_user_event_handler.await.unwrap();

    println!("Blockchain application stopped. Goodbye!");
}

async fn start_mining_task(
    args: &Args,
    bc_manager: BcManagerHandler,
    mining_cmd_receiver: mpsc::Receiver<MiningCommand>,
    transaction_receiver: mpsc::Receiver<Transaction>,
    shutdown_signal: watch::Receiver<bool>,
) -> std::thread::JoinHandle<()> {
    mini_block_chain::start_mining_task(
        args,
        bc_manager,
        mining_cmd_receiver,
        transaction_receiver,
        shutdown_signal,
    )
}

async fn start_input_listener_task(bc_manager: BcManagerHandler) -> tokio::task::JoinHandle<()> {
    tokio::task::spawn(async move {
        let mut stdin = tokio::io::BufReader::new(tokio::io::stdin()).lines();
        print!("> ");
        std::io::stdout().flush().unwrap();
        while let Ok(Some(line)) = stdin.next_line().await {
            let line = line.trim();
            if !line.is_empty() {
                debug!("Received input: {}", line);
            }

            if debug_cmd::process_input_listener(bc_manager.clone(), line).await {
                break;
            }
            print!("> ");
            std::io::stdout().flush().unwrap();
        }
    })
}
